/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package marcin.belzowski.gui;

import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JToolBar;
import org.geotools.data.FeatureSource;
import org.geotools.data.FileDataStore;
import org.geotools.data.FileDataStoreFinder;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.filter.text.cql2.CQLException;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.DefaultMapContext;
import org.geotools.map.MapContext;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.styling.FeatureTypeStyle;
import org.geotools.styling.Fill;
import org.geotools.styling.Graphic;
import org.geotools.styling.Mark;
import org.geotools.styling.Rule;
import org.geotools.styling.Stroke;
import org.geotools.styling.Style;
import org.geotools.styling.StyleFactory;
import org.geotools.styling.Symbolizer;

import org.geotools.swing.JMapPane;
import org.geotools.swing.event.MapMouseEvent;
import org.geotools.swing.tool.CursorTool;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.identity.FeatureId;

/**
 *
 * @author marcin
 */
public class StreetsWindow {


    /*
     * Factories that we will use to create style and filter objects
     */
    private StyleFactory sf = CommonFactoryFinder.getStyleFactory(null);
    private FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);

    /*
     * Convenient constants for the type of feature geometry in the shapefile
     */
    private enum GeomType {

        POINT, LINE, POLYGON
    };

    /*
     * Some default style variables
     */
    private static final Color LINE_COLOUR = Color.BLUE;
    private static final Color FILL_COLOUR = Color.CYAN;
    private static Color SELECTED_COLOUR = Color.BLACK;
    private static Color MaxF = Color.RED;
    private static Color MIDDLE = Color.YELLOW;
    private static Color MinF = Color.GREEN;
    private static final float OPACITY = 1.0f;
    private static final float LINE_WIDTH = 4.0f;
    private static final float POINT_SIZE = 10.0f;
    private static final int RAY = 30;
    Boolean isMax = false;
    private JMapFrame mapFrame;
    private FeatureSource<SimpleFeatureType, SimpleFeature> featureSource;
    private String geometryAttributeName;
    private GeomType geometryType;
    JMapPane mapPane = new JMapPane();
    private final String filePath = "./ul1/ul1.shp";
    /*
     * The application method
     */
    private ArrayList<String> streetsSelected = new ArrayList<String>();

    public ArrayList<String> getStreetsSelected() {
        return streetsSelected;
    }

    public StreetsWindow(JMapPane mapPane) {
        this.mapPane = mapPane;
    }

    public StreetsWindow() {
    }

    public void showMap() throws Exception {
        StreetsWindow me = new StreetsWindow();

        File file = new File(filePath);
//                JFileDataStoreChooser.showOpenFile("shp", null);
        if (file == null) {
            return;
        }

        /*
         * Create the JMapFrame and set it to display the shapefile's features
         * with a default line and colour style
         */


        displayShapeFileOnPanel(file);

    }

    public static void main(String[] args) throws Exception {
        StreetsWindow me = new StreetsWindow();

//        File file = new File(filePath);
//                JFileDataStoreChooser.showOpenFile("shp", null);
//        if (file == null) {
//            return;
//        }
//
//        me.displayShapefile(file);
    }
// docs end main

// docs start display shapefile
    /**
     * This method connects to the shapefile; retrieves information about
     * its features; creates a map frame to display the shapefile and adds
     * a custom feature selection tool to the toolbar of the map frame.
     */
    public void displayShapeFileOnPanel(File file) throws IOException {
        FileDataStore store = FileDataStoreFinder.getDataStore(file);
        featureSource = store.getFeatureSource();
        setGeometry();

        /*
         * Create the JMapFrame and set it to display the shapefile's features
         * with a default line and colour style
         */
        MapContext map = new DefaultMapContext();
        map.setTitle("Mapa miasta");
        Style style = createDefaultStyle();
        map.addLayer(featureSource, style);
        mapPane.setBackground(Color.WHITE);
        mapPane.setMapContext(map);
        mapPane.setRenderer(new StreamingRenderer());
        mapPane.setCursorTool(
                new CursorTool() {

                    @Override
                    public void onMouseClicked(MapMouseEvent ev) {
                        try {
                            try {
                                selectFeatures(ev);
                            } catch (ParseException ex) {
                                Logger.getLogger(StreetsWindow.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        } catch (CQLException ex) {
                            Logger.getLogger(StreetsWindow.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                });

    }

    public void displayShapefile(File file) throws Exception {
        FileDataStore store = FileDataStoreFinder.getDataStore(file);
        featureSource = store.getFeatureSource();
        setGeometry();

        /*
         * Create the JMapFrame and set it to display the shapefile's features
         * with a default line and colour style
         */
        MapContext map = new DefaultMapContext();
        map.setTitle("Mapa miasta");
        Style style = createDefaultStyle();
        map.addLayer(featureSource, style);
        mapFrame = new JMapFrame(map);
        mapFrame.enableToolBar(true);
        mapFrame.enableStatusBar(true);





        /*
         * Before making the map frame visible we add a new button to its
         * toolbar for our custom feature selection tool
         */
        JToolBar toolBar = mapFrame.getToolBar();
        JButton btn = new JButton("Select");
        toolBar.addSeparator();
        toolBar.add(btn);

        /*
         * When the user clicks the button we want to enable
         * our custom feature selection tool. Since the only
         * mouse action we are intersted in is 'clicked', and
         * we are not creating control icons or cursors here,
         * we can just create our tool as an anonymous sub-class
         * of CursorTool.
         */
        btn.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                mapFrame.getMapPane().setCursorTool(
                        new CursorTool() {

                            @Override
                            public void onMouseClicked(MapMouseEvent ev) {
                                try {
                                    try {
                                        selectFeatures(ev);
                                    } catch (ParseException ex) {
                                        Logger.getLogger(StreetsWindow.class.getName()).log(Level.SEVERE, null, ex);
                                    }
                                } catch (CQLException ex) {
                                    Logger.getLogger(StreetsWindow.class.getName()).log(Level.SEVERE, null, ex);
                                }
                            }
                        });
            }
        });

        /**
         * Finally, we display the map frame. When it is closed
         * this application will exit.
         */
        mapFrame.setSize(600, 600);
        mapFrame.setVisible(true);
    }
// docs end display shapefile

// docs start select features
    /**
     * This method is called by our feature selection tool when
     * the user has clicked on the map.
     *
     * @param pos map (world) coordinates of the mouse cursor
     */
    void selectFeatures(MapMouseEvent ev) throws CQLException, ParseException {

//        System.out.println("Mouse click at: " + ev.getMapPosition());

        /*
         * Construct a 5x5 pixel rectangle centred on the mouse click position
         */
        Point screenPos = ev.getPoint();
        Rectangle screenRect = new Rectangle(screenPos.x - 2, screenPos.y - 2, 5, 5);

        /*
         * Transform the screen rectangle into bounding box in the coordinate
         * reference system of our map context. Note: we are using a naive method
         * here but GeoTools also offers other, more accurate methods.
         */
        AffineTransform screenToWorld = mapPane.getScreenToWorldTransform();
        Rectangle2D worldRect = screenToWorld.createTransformedShape(screenRect).getBounds2D();
        ReferencedEnvelope bbox = new ReferencedEnvelope(
                worldRect,
                mapPane.getMapContext().getCoordinateReferenceSystem());

        /*
         * Create a Filter to select features that intersect with
         * the bounding box
         */
//        Filter filter = ff.bbox(ff.property(geometryAttributeName), bbox);
        Double x = ev.getMapPosition().x;
        Double y = ev.getMapPosition().y;
        String xS = x.toString();
        String yS = y.toString();
        String pointS = "POINT(" + xS + " " + yS + ")"; //"LINESTRING(-1612.8 88.005693950172781,10.37081850534 88.0056939501781,1027.37081850534 88.0056939501781)";

        ArrayList<LineString> arrayLine = getNeighbourPoints(ev, RAY);
        Filter filter = CQL.toFilter("CROSSES(" + geometryAttributeName + "," + arrayLine.get(0) + ")");
//        Filter filter = CQL.toFilter("INTERSECT(the_geom,LINESTRING(-1612.8 88.005693950172781,10.37081850534 88.0056939501781,1027.37081850534 88.0056939501781))");

        /*
         * Use the filter to identify the selected features
         */
        try {
            FeatureCollection<SimpleFeatureType, SimpleFeature> selectedFeatures =
                    featureSource.getFeatures(filter);

            FeatureIterator<SimpleFeature> iter = selectedFeatures.features();
            Set<FeatureId> IDs = new HashSet<FeatureId>();
            streetsSelected = new ArrayList<String>();

            try {
                while (iter.hasNext()) {
                    SimpleFeature feature = iter.next();
                    IDs.add(feature.getIdentifier());
                    List<Object> tmp = feature.getAttributes();
                    streetsSelected.add((String) tmp.get(1));

                }
//                System.out.println(streetsSelected);

            } finally {
                iter.close();
            }

            if (IDs.isEmpty()) {
//                System.out.println("   no feature selected");
            }

            displaySelectedFeatures(IDs);

        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
    }

    public void selectFeatures(String name) throws CQLException {

        Filter filter = CQL.toFilter("StreetLaye like '" + name + "'");
//        System.out.println(featureSource.getSchema().getAttributeDescriptors());
//        Filter filter = CQL.toFilter("INTERSECT(the_geom,LINESTRING(-1612.8 88.005693950172781,10.37081850534 88.0056939501781,1027.37081850534 88.0056939501781))");

        /*
         * Use the filter to identify the selected features
         */
        try {
            FeatureCollection<SimpleFeatureType, SimpleFeature> selectedFeatures =
                    featureSource.getFeatures(filter);

            FeatureIterator<SimpleFeature> iter = selectedFeatures.features();
            Set<FeatureId> IDs = new HashSet<FeatureId>();
            streetsSelected = new ArrayList<String>();

            try {
                while (iter.hasNext()) {
                    SimpleFeature feature = iter.next();
                    IDs.add(feature.getIdentifier());
                    List<Object> tmp = feature.getAttributes();
                    streetsSelected.add((String) tmp.get(1));

                }
                System.out.println(streetsSelected);

            } finally {
                iter.close();
            }



            displaySelectedFeatures(IDs);

        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
    }

    public void selectFeatures(String nameMax, String nameMin) throws CQLException {

        Filter filterMax = CQL.toFilter("StreetLaye like '" + nameMax + "'");
        Filter filterMin = CQL.toFilter("StreetLaye like '" + nameMin + "'");
//        System.out.println(featureSource.getSchema().getAttributeDescriptors());
//        Filter filter = CQL.toFilter("INTERSECT(the_geom,LINESTRING(-1612.8 88.005693950172781,10.37081850534 88.0056939501781,1027.37081850534 88.0056939501781))");

        /*
         * Use the filter to identify street names as nameMax
         */
        try {
            FeatureCollection<SimpleFeatureType, SimpleFeature> maxFeatures =
                    featureSource.getFeatures(filterMax);
            FeatureCollection<SimpleFeatureType, SimpleFeature> minFeatures =
                    featureSource.getFeatures(filterMin);
            FeatureIterator<SimpleFeature> iterMax = maxFeatures.features();
            Set<FeatureId> IDsMax = new HashSet<FeatureId>();

            FeatureIterator<SimpleFeature> iterMin = minFeatures.features();
            Set<FeatureId> IDsMin = new HashSet<FeatureId>();

            try {
                while (iterMax.hasNext()) {
                    SimpleFeature feature = iterMax.next();
                    IDsMax.add(feature.getIdentifier());
                }
                while (iterMin.hasNext()) {
                    SimpleFeature feature = iterMin.next();
                    IDsMin.add(feature.getIdentifier());
                }


            } finally {
                iterMax.close();
                iterMin.close();
            }





            displayFrequency(IDsMin, IDsMax);

        } catch (Exception ex) {
            ex.printStackTrace();
            return;
        }
    }

    public void displayFrequency(Set<FeatureId> IDsMin, Set<FeatureId> IDsMax) {
        Style style;


        style = createFrequencyStyle(IDsMin, IDsMax);
        mapPane.getMapContext().getLayer(0).setStyle(style);
        mapPane.repaint();



    }


    private Style createFrequencyStyle(Set<FeatureId> IDsMin, Set<FeatureId> IDsMax) {
         Rule minRule = createRule(MinF, MinF);
        minRule.setFilter(ff.id(IDsMin));

        Rule maxRule = createRule(MaxF, MaxF);
        maxRule.setFilter(ff.id(IDsMax));

        Rule otherRule = createRule(LINE_COLOUR, FILL_COLOUR);
        otherRule.setElseFilter(true);

        FeatureTypeStyle fts = sf.createFeatureTypeStyle();
        fts.rules().add(minRule);
        fts.rules().add(maxRule);
        fts.rules().add(otherRule);

        Style style = sf.createStyle();
        style.featureTypeStyles().add(fts);
        return style;
    }

// docs end select features
// docs start display selected
    /**
     * Sets the display to paint selected features yellow and
     * unselected features in the default style.
     *
     * @param IDs identifiers of currently selected features
     */
    public void displaySelectedFeatures(Set<FeatureId> IDs) {
        Style style;

        if (IDs.isEmpty()) {
            style = createDefaultStyle();

        } else {
            style = createSelectedStyle(IDs);
            mapPane.getMapContext().getLayer(0).setStyle(style);
            mapPane.repaint();
        }


    }

// docs end display selected
// docs start default style
    /**
     * Create a default Style for feature display
     */
    private Style createDefaultStyle() {
        Rule rule = createRule(LINE_COLOUR, FILL_COLOUR);

        FeatureTypeStyle fts = sf.createFeatureTypeStyle();
        fts.rules().add(rule);

        Style style = sf.createStyle();
        style.featureTypeStyles().add(fts);
        return style;
    }

   
// docs end default style

// docs start selected style
    /**
     * Create a Style where features with given IDs are painted
     * yellow, while others are painted with the default colors.
     */
    private Style createSelectedStyle(Set<FeatureId> IDs) {
        Rule selectedRule = createRule(SELECTED_COLOUR, Color.BLUE);
        selectedRule.setFilter(ff.id(IDs));

        Rule otherRule = createRule(LINE_COLOUR, FILL_COLOUR);
        otherRule.setElseFilter(true);

        FeatureTypeStyle fts = sf.createFeatureTypeStyle();
        fts.rules().add(selectedRule);
        fts.rules().add(otherRule);

        Style style = sf.createStyle();
        style.featureTypeStyles().add(fts);
        return style;
    }
// docs end selected style

// docs start create rule
    /**
     * Helper for createXXXStyle methods. Creates a new Rule containing
     * a Symbolizer tailored to the geometry type of the features that
     * we are displaying.
     */
    private Rule createRule(Color outlineColor, Color fillColor) {
        Symbolizer symbolizer = null;
        Fill fill = null;
        Stroke stroke = sf.createStroke(ff.literal(outlineColor), ff.literal(LINE_WIDTH));

        switch (geometryType) {
            case POLYGON:
                fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY));
                symbolizer = sf.createPolygonSymbolizer(stroke, fill, geometryAttributeName);
                break;

            case LINE:
                symbolizer = sf.createLineSymbolizer(stroke, geometryAttributeName);
                break;

            case POINT:
                fill = sf.createFill(ff.literal(fillColor), ff.literal(OPACITY));

                Mark mark = sf.getCircleMark();
                mark.setFill(fill);
                mark.setStroke(stroke);

                Graphic graphic = sf.createDefaultGraphic();
                graphic.graphicalSymbols().clear();
                graphic.graphicalSymbols().add(mark);
                graphic.setSize(ff.literal(POINT_SIZE));

                symbolizer = sf.createPointSymbolizer(graphic, geometryAttributeName);
        }

        Rule rule = sf.createRule();
        rule.symbolizers().add(symbolizer);
        return rule;
    }
// docs end create rule

// docs start set geometry
    /**
     * Retrieve information about the feature geometry
     */
    private void setGeometry() {
        GeometryDescriptor geomDesc = featureSource.getSchema().getGeometryDescriptor();
        geometryAttributeName = geomDesc.getLocalName();

        Class<?> clazz = geomDesc.getType().getBinding();

        if (Polygon.class.isAssignableFrom(clazz) ||
                MultiPolygon.class.isAssignableFrom(clazz)) {
            geometryType = GeomType.POLYGON;

        } else if (LineString.class.isAssignableFrom(clazz) ||
                MultiLineString.class.isAssignableFrom(clazz)) {

            geometryType = GeomType.LINE;

        } else {
            geometryType = GeomType.POINT;
        }

    }
// docs end set geometry

    private ArrayList<LineString> getNeighbourPoints(MapMouseEvent ev, int ray) throws ParseException {
        Double x = ev.getMapPosition().x;
        Double y = ev.getMapPosition().y;
        Double x2 = x + ray;
        Double y2 = y + ray;
        x -= ray;
        y -= ray;
        String xS = x.toString();
        String yS = y.toString();
        String xE = x2.toString();
        String yE = y2.toString();

        GeometryFactory geometryFactory = new GeometryFactory();
        WKTReader reader = new WKTReader(geometryFactory);
        LineString line = (LineString) reader.read("LINESTRING(" + xS + " " + yS + "," + xE + " " + yE + ")");
        line.setSRID(4326);
        System.out.println(line);
        ArrayList<LineString> arrayLine = new ArrayList<LineString>();

        arrayLine.add(line);

        return arrayLine;



    }
}
